/*
 * Copyright (c) 1989 - 1999 Microsoft Corporation
 */

#include "precomp.h"
#pragma hdrstop

#include "mrxglobs.h"

static
NTSTATUS
HwFsDevIoctl(
    IN OUT PRX_CONTEXT RxContext,
    ULONG ControlCode
);

static
BOOLEAN
ParseRDRName(PUNICODE_STRING pRdrName,
    PUNICODE_STRING pLocalName,
    PUNICODE_STRING pRemoteName,
    PUNICODE_STRING pFolderName
);

static
HWFS_CONNECTION*
FindConnectionLocked(
    IN PHWFS_DEVICE_EXTENSION  pDevExt,
    PUNICODE_STRING pRdrName
);

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, HwFsDevFcbXXXControlFile)
#pragma alloc_text(PAGE, HwFsDevIoctl)
#pragma alloc_text(PAGE, ParseRDRName)
#pragma alloc_text(PAGE, FindConnectionLocked)

#endif

/* Extract share name from string like
 * \\device\miniredirector\;<DriveLetter>:\Server\Share
 * or
 * \\device\miniredirector\;SomePath\Server\Share
 * or
 * \\device\miniredirector\;\Server\Share
 */
static
BOOLEAN
ParseRDRName(PUNICODE_STRING pRdrName,
             PUNICODE_STRING pLocalName,  // drive letter
             PUNICODE_STRING pRemoteName, // full remote name, like \Server\Share
             PUNICODE_STRING pFolderName  // Folder name, i.e. "Share"
)
{
    WCHAR* pwlocal;
    LONG SkippedLen;
    LONG SharePathLen;
    WCHAR* pw = pRdrName->Buffer;
    WCHAR* end = pRdrName->Buffer + pRdrName->Length / sizeof(WCHAR);

    // Skip all up to ';'
    while (pw < end && *pw != ';') {
        pw++;
    }
    pw++;
    if (pw >= end)
        return FALSE;

    pwlocal = pw; // pw now points to LocalName (or to '\' if no local mapping)

    // Find next '\\'
    while (pw < end && *pw != '\\') {
        pw++;
    }
    if (pw >= end)
        return FALSE;

    // pw now points to remote name

    // Local can be empty.
    pLocalName->Buffer = pwlocal;
    pLocalName->Length = pLocalName->MaximumLength = (USHORT) (pw - pwlocal) * sizeof(WCHAR);

    SkippedLen = (LONG)(sizeof(WCHAR) * (pw - pRdrName->Buffer));
    pRemoteName->Buffer = pw;
    pRemoteName->Length = pRemoteName->MaximumLength = (USHORT) (pRdrName->Length - SkippedLen);

    // Skip current '\\' and find next '\\'.
    pw++;
    while (pw < end && *pw != '\\') {
        pw++;
    }
    pw++; // pw now points to shared folder path
    if (pw >= end) {
        return FALSE;
    }

    pFolderName->Buffer = pw;

    SharePathLen = 0;
    // Find length till the end of the string or till next '\\'
    while (pw < end && *pw != '\\') {
        pw++;
        SharePathLen++;
    }

    pFolderName->Length = pFolderName->MaximumLength = (USHORT) SharePathLen * sizeof(WCHAR);

    return TRUE;
}

static
HWFS_CONNECTION *
FindConnectionLocked(
    IN PHWFS_DEVICE_EXTENSION  pDevExt,
    PUNICODE_STRING pRdrName
)
{
    LIST_ENTRY* pEntry;

    for (pEntry = pDevExt->ConnectionList.Flink;
            pEntry != &pDevExt->ConnectionList;
            pEntry = pEntry->Flink) {
        HWFS_CONNECTION* pConn = CONTAINING_RECORD(pEntry, HWFS_CONNECTION, Link);
        if (RtlEqualUnicodeString(&pConn->RdrName, pRdrName, TRUE)) {
            return pConn;
        }
    }
    return NULL;
}

static
VOID
AddConnection(
    IN PHWFS_DEVICE_EXTENSION  pDevExt,
    IN PUNICODE_STRING         pRdrName
)
{
    UNICODE_STRING RemoteName;
    UNICODE_STRING LocalName;
    UNICODE_STRING FolderName;
    HWFS_CONNECTION* pConn;

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("AddConnection: %wZ\n", pRdrName));
    if (!ParseRDRName(pRdrName, &LocalName, &RemoteName, &FolderName)
            || LocalName.Length == 0) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("Connection skipped\n"));
        return; // No need to remember connections without drive letter or in unknown format
    }

    ULONG AllocSize = sizeof(HWFS_CONNECTION) + pRdrName->Length + sizeof(WCHAR); // with trailing zero
#pragma warning(disable:4996)
    pConn = ExAllocatePoolWithTag(PagedPool, AllocSize, 'nCfS');
    if (!pConn) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_SESSION, ("HwFs Connection alloc failed\n"));
        return;
    }

    pConn->RdrName.Buffer = (WCHAR*)(pConn + 1);
    pConn->RdrName.Length = 0;
    pConn->RdrName.MaximumLength = (USHORT) AllocSize - sizeof(*pConn);
    RtlCopyUnicodeString(&pConn->RdrName, pRdrName);

    ParseRDRName(&pConn->RdrName, &pConn->LocalName, &pConn->RemoteName, &pConn->FolderName);

    ExAcquireFastMutex(&pDevExt->LCMutex);
    if (!FindConnectionLocked(pDevExt, pRdrName)) {
        InsertTailList(&pDevExt->ConnectionList, &pConn->Link);
    } else {
        ExFreePoolWithTag(pConn, 'nCfS');
    }
    ExReleaseFastMutex(&pDevExt->LCMutex);
}

static
ULONG
MakeConnectionNameListLocked(
    IN  PHWFS_DEVICE_EXTENSION    pDevExt,
    OUT PWCHAR                    pNameListBuf,
    IN  ULONG                     uBufSize
)
{
    LIST_ENTRY* pEntry;
    ULONG       uSize;
    UCHAR* cp;
    UCHAR* end;

    uSize = 0;
    cp = (UCHAR*)pNameListBuf;
    end = cp + uBufSize;

    for (pEntry = pDevExt->ConnectionList.Flink;
            pEntry != &pDevExt->ConnectionList;
            pEntry = pEntry->Flink) {
        HWFS_CONNECTION* pConn = CONTAINING_RECORD(pEntry, HWFS_CONNECTION, Link);
        ULONG CopySize = pConn->RdrName.Length + sizeof(WCHAR);
        uSize += CopySize;

        if (cp != NULL && cp + CopySize <= end) {
            RtlCopyMemory(cp, pConn->RdrName.Buffer, CopySize - sizeof(WCHAR));
            cp += CopySize - sizeof(WCHAR);
            *cp++ = 0;
            *cp++ = 0;
        }
    }

    // finishing 0
    uSize += sizeof(WCHAR);
    if (cp != NULL && cp + sizeof(WCHAR) <= end) {
        *cp++ = 0;
        *cp++ = 0;
    }
    return uSize;
}

static
VOID
RemoveConnection(
    IN PHWFS_DEVICE_EXTENSION    pDevExt,
    IN PUNICODE_STRING           pRdrName
)
{
    LIST_ENTRY* pEntry;

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("RemoveConnection: %wZ\n", pRdrName));

    ExAcquireFastMutex(&pDevExt->LCMutex);
    for (pEntry = pDevExt->ConnectionList.Flink;
            pEntry != &pDevExt->ConnectionList;
            pEntry = pEntry->Flink) {
        UNICODE_STRING us;
        HWFS_CONNECTION *pConn = CONTAINING_RECORD(pEntry, HWFS_CONNECTION, Link);
        if (pConn->RdrName.Length < pRdrName->Length) {
            continue;
        }
        // Compare first symbols
        us.Length = us.MaximumLength = pRdrName->Length;
        us.Buffer = pConn->RdrName.Buffer;
        if (RtlEqualUnicodeString(&us, pRdrName, TRUE)) {
            RemoveEntryList(&pConn->Link);
            ExFreePoolWithTag(pConn, 'nCfS');
            break;
        }
    }

    ExReleaseFastMutex(&pDevExt->LCMutex);
}

static
HWFS_CONNECTION *
FindConnectionByLocalNameLocked(
    IN PHWFS_DEVICE_EXTENSION    pDevExt,
    IN PUNICODE_STRING           pLocalName
)
{
    LIST_ENTRY* pEntry;

    for (pEntry = pDevExt->ConnectionList.Flink;
            pEntry != &pDevExt->ConnectionList;
            pEntry = pEntry->Flink) {
        HWFS_CONNECTION* pConn = CONTAINING_RECORD(pEntry, HWFS_CONNECTION, Link);
        if (RtlEqualUnicodeString(&pConn->LocalName, pLocalName, FALSE)) {
            return pConn;
        }
    }
    return NULL;
}

static
NTSTATUS
HwFsGetConnectionList(
    IN  PHWFS_DEVICE_EXTENSION pDeviceExtension,
    IN  PVOID OutBuffer,
    IN  ULONG OutBufferSize,
    OUT ULONG_PTR *pBytesReturned)
{
    ULONG BytesReturned;

    if (OutBufferSize < sizeof(ULONG)) {
        *pBytesReturned = 0;
        return STATUS_BUFFER_TOO_SMALL;
    }

    ExAcquireFastMutex(&pDeviceExtension->LCMutex);
    BytesReturned = MakeConnectionNameListLocked(
        pDeviceExtension, OutBuffer, OutBufferSize);
    ExReleaseFastMutex(&pDeviceExtension->LCMutex);

    if (BytesReturned > OutBufferSize) {
        *pBytesReturned = sizeof(ULONG);
        *(ULONG*)OutBuffer = BytesReturned;
        return STATUS_BUFFER_OVERFLOW;
    } else {
        *pBytesReturned = BytesReturned;
        return STATUS_SUCCESS;
    }
}

static
NTSTATUS
HwFsGetConnection(
    IN  PHWFS_DEVICE_EXTENSION pDeviceExtension,
    IN  PWCHAR  ConnectName,
    IN  ULONG   ConnectNameLen,
    OUT PWCHAR  RemoteName,
    IN  ULONG   RemoteNameLen,
    OUT ULONG_PTR* pBytesReturned)
{
    HWFS_CONNECTION* pConn;
    UNICODE_STRING usLocal;
    NTSTATUS Status;

    *pBytesReturned = 0;
    if (ConnectNameLen < sizeof(WCHAR)) {
        return STATUS_BAD_NETWORK_NAME;
    }

    Status = STATUS_BAD_NETWORK_NAME;

    usLocal.Buffer = ConnectName;
    usLocal.Length = usLocal.MaximumLength = (USHORT)ConnectNameLen - sizeof(WCHAR);

    ExAcquireFastMutex(&pDeviceExtension->LCMutex);
    pConn = FindConnectionByLocalNameLocked(pDeviceExtension, &usLocal);
    if (pConn) {
        if (RemoteNameLen < pConn->RemoteName.Length + sizeof(WCHAR)) {
            Status = STATUS_BUFFER_TOO_SMALL;
        } else {
            RtlCopyMemory(RemoteName, pConn->RemoteName.Buffer, pConn->RemoteName.Length);
            RemoteName[pConn->RemoteName.Length / sizeof(WCHAR)] = L'\0';
            *pBytesReturned = pConn->RemoteName.Length + sizeof(WCHAR);
            Status = STATUS_SUCCESS;
        }
    }
    ExReleaseFastMutex(&pDeviceExtension->LCMutex);

    return Status;
}

static
NTSTATUS
HwFsDevIoctl(
    IN OUT PRX_CONTEXT RxContext,
    ULONG ControlCode)
{
    NTSTATUS Status;
    PLOWIO_CONTEXT LowIoContext = &RxContext->LowIoContext;
    PHWFS_DEVICE_EXTENSION pDeviceExtension;

    pDeviceExtension = HWFS_GET_DEVICE_EXTENSION(RxContext);

    HWFS_DBG_PRINT(DBG_INFO, DBG_IOCTL, ("HwFsDevIoctl %08lx\n", ControlCode));

    switch (ControlCode) {
        case IOCTL_HWFS_ADDCONN:
            Status = HwFsCreateConnection(RxContext, &RxContext->PostRequest);
            break;

        case IOCTL_HWFS_DELCONN:
            Status = HwFsDeleteConnection(RxContext, &RxContext->PostRequest);
            break;

        case IOCTL_HWFS_GETLIST:
            Status = HwFsGetConnectionList(
                pDeviceExtension,
                LowIoContext->ParamsFor.IoCtl.pOutputBuffer,
                LowIoContext->ParamsFor.IoCtl.OutputBufferLength,
                &RxContext->InformationToReturn);
            break;

        case IOCTL_HWFS_GETCONN:
            Status = HwFsGetConnection(
                pDeviceExtension,
                (PWCHAR)LowIoContext->ParamsFor.IoCtl.pInputBuffer,
                LowIoContext->ParamsFor.IoCtl.InputBufferLength,
                (PWCHAR)LowIoContext->ParamsFor.IoCtl.pOutputBuffer,
                LowIoContext->ParamsFor.IoCtl.OutputBufferLength,
                &RxContext->InformationToReturn);
            break;

        default:
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_IOCTL, ("HwFsDevIoctl Status 0x%x\n", Status));
    return Status;
}

NTSTATUS
HwFsDevFcbXXXControlFile(
    IN OUT PRX_CONTEXT RxContext)
{
    NTSTATUS Status;
    UCHAR MajorFunctionCode  = RxContext->MajorFunction;
    PLOWIO_CONTEXT LowIoContext  = &RxContext->LowIoContext;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_IOCTL, ("HwFsDevFcb\n"));

    switch (MajorFunctionCode) {
        case IRP_MJ_FILE_SYSTEM_CONTROL:
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;

        case IRP_MJ_DEVICE_CONTROL:
            Status = HwFsDevIoctl(RxContext, LowIoContext->ParamsFor.IoCtl.IoControlCode);
            break;

        case IRP_MJ_INTERNAL_DEVICE_CONTROL:
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;

        default:
            ASSERT(!"unimplemented major function");
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_IOCTL,
               ("HwFsDevFcb st,info=%08lx,%08lx\n",
               Status, RxContext->InformationToReturn));
    return(Status);
}


HANDLE
GetConnectionHandle(
    IN PUNICODE_STRING  ConnectionName,
    IN BOOLEAN          IsCreate)
{
    NTSTATUS            Status;
    HANDLE              Handle;
    IO_STATUS_BLOCK     IoStatusBlock;
    OBJECT_ATTRIBUTES   ObjectAttributes;

    // Connection name should get checked to be certain our device is in the path
    InitializeObjectAttributes(
        &ObjectAttributes,
        ConnectionName,
        OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
        NULL,
        NULL);

    Status = ZwCreateFile(
        &Handle,
        SYNCHRONIZE,
        &ObjectAttributes,
        &IoStatusBlock,
        NULL,           // Allocation size
        FILE_ATTRIBUTE_NORMAL,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        IsCreate ? FILE_OPEN_IF : FILE_OPEN,
        FILE_CREATE_TREE_CONNECTION | FILE_SYNCHRONOUS_IO_NONALERT,
        NULL,           // Ptr to EA Buffer
        0);             // Length of EA buffer

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION,
        ("GetConnection(%d): ZwCreateFile returned 0x%lx\n", IsCreate, Status));

    if (Status != STATUS_SUCCESS || Handle == INVALID_HANDLE_VALUE) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_SESSION,
            ("GetConnection(%d): ZwCreateFile returned error 0x%lx\n", IsCreate, Status));
        Handle = INVALID_HANDLE_VALUE;
    }

    return  Handle;
}

NTSTATUS
DoCreateConnection(
    IN PRX_CONTEXT RxContext,
    ULONG   CreateDisposition
    )
{
    (VOID)CreateDisposition;

    NTSTATUS Status = STATUS_SUCCESS;
    HANDLE          Handle;
    PLOWIO_CONTEXT  LowIoContext       = &RxContext->LowIoContext;
    ULONG           ConnectNameLen     = LowIoContext->ParamsFor.IoCtl.InputBufferLength;
    PWCHAR          ConnectName        = LowIoContext->ParamsFor.IoCtl.pInputBuffer;
    UNICODE_STRING  FileName;
    PHWFS_DEVICE_EXTENSION pDeviceExtension;

    pDeviceExtension = HWFS_GET_DEVICE_EXTENSION(RxContext);

    // The sample code passes in only the filename in the Ioctl data buffer.
    //  An actual implementation could pass in stuff like EAs, security
    // credentials, etc

    HWFS_DBG_PRINT(DBG_INFO, DBG_IOCTL, ("DoCreateConnection\n"));
    if (ConnectNameLen < sizeof(WCHAR)) {
        return STATUS_INVALID_DEVICE_REQUEST;
    }

    FileName.Buffer = ConnectName;
    FileName.Length = FileName.MaximumLength = (USHORT)ConnectNameLen - sizeof(WCHAR);

    /*
     * The FileName is in format
     * \\device\miniredirector\;<DriveLetter>:\Server\Share
     */
    Handle = GetConnectionHandle(&FileName, TRUE /*Create*/);
    if (INVALID_HANDLE_VALUE != Handle) {
        PWCHAR pwcLC;
        ULONG i;

        HWFS_DBG_PRINT(DBG_INFO, DBG_IOCTL, ("DoCreateConnection() success\n"));
        ZwClose(Handle);
        AddConnection(pDeviceExtension, &FileName);
    } else {
        HWFS_DBG_PRINT(DBG_ERR, DBG_IOCTL, ("CreateConnection failure\n"));
        Status = STATUS_BAD_NETWORK_NAME;
    }

    return Status;
}


NTSTATUS
HwFsCreateConnection (
    IN PRX_CONTEXT RxContext,
    OUT PBOOLEAN PostToFsp
    )
/*++

Arguments:

    IN PRX_CONTEXT RxContext - Describes the Fsctl and Context....for later when i need the buffers

Return Value:

RXSTATUS

--*/
{
    NTSTATUS Status = STATUS_SUCCESS;

    BOOLEAN Wait = BooleanFlagOn(RxContext->Flags, RX_CONTEXT_FLAG_WAIT);

    PAGED_CODE();

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("HwFsCreateConnection\n"));

    if (!Wait) {
        //just post right now!
        *PostToFsp = TRUE;
        return STATUS_PENDING;
    }

    Status = DoCreateConnection(RxContext, (ULONG)FILE_OPEN_IF);

    return Status;
}

static
NTSTATUS
DoDeleteConnection(
    PUNICODE_STRING pFileName
    )
{
    HANDLE          Handle;
    NTSTATUS        Status;
    PFILE_OBJECT    pFileObject;

    Handle = GetConnectionHandle(pFileName, FALSE /*Get*/);
    if (Handle == INVALID_HANDLE_VALUE) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("DoDeleteConnection: Failed to get ConnectionHandle\n"));
        return STATUS_INVALID_HANDLE;
    }

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("DoDeleteConnection: ConnectionHandle is ok\n"));

    Status = ObReferenceObjectByHandle(
            Handle, 0L, NULL, KernelMode, (PVOID *)&pFileObject, NULL);
    if (NT_SUCCESS(Status)) {
        PV_NET_ROOT     pVNetRoot;

        pVNetRoot = pFileObject->FsContext2;
        if (pVNetRoot && NodeType(pVNetRoot) == RDBSS_NTC_V_NETROOT) {
            HWFS_DBG_PRINT(DBG_TRACE, DBG_SESSION, ("Calling RxFinalizeConnection\n"));
            Status = RxFinalizeConnection(pVNetRoot->NetRoot, pVNetRoot, TRUE);
        } else {
            HWFS_DBG_PRINT(DBG_WARN, DBG_SESSION,
                ("DoDeleteConnection: VNetRoot isn't valid on connection\n"));
            Status = STATUS_INVALID_DEVICE_REQUEST;
        }

        ObDereferenceObject(pFileObject);
    }

    ZwClose(Handle);

    return Status;
}


NTSTATUS
HwFsDeleteConnection (
    IN PRX_CONTEXT RxContext,
    OUT PBOOLEAN PostToFsp
    )
/*++

Routine Description:

    This routine deletes a single vnetroot.

Arguments:

    IN PRX_CONTEXT RxContext - Describes the Fsctl and Context....for later when i need the buffers

Return Value:

RXSTATUS

--*/
{
    UNICODE_STRING  FileName;

    BOOLEAN         Wait              = BooleanFlagOn(RxContext->Flags, RX_CONTEXT_FLAG_WAIT);
    PLOWIO_CONTEXT  LowIoContext      = &RxContext->LowIoContext;
    PHWFS_DEVICE_EXTENSION pDeviceExtension;

    pDeviceExtension = HWFS_GET_DEVICE_EXTENSION(RxContext);

    PAGED_CODE();

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("HwFsDeleteConnection\n"));

    if (!Wait) {
        //just post right now!
        *PostToFsp = TRUE;
        return(STATUS_PENDING);
    }

    if (LowIoContext->ParamsFor.IoCtl.InputBufferLength < sizeof(WCHAR)) {
        return STATUS_INVALID_DEVICE_REQUEST;
    }

    FileName.Buffer = LowIoContext->ParamsFor.IoCtl.pInputBuffer;
    FileName.Length = FileName.MaximumLength =
        (USHORT)LowIoContext->ParamsFor.IoCtl.InputBufferLength - sizeof(WCHAR); // name is with trailing zero

    DoDeleteConnection(&FileName);

    RemoveConnection(pDeviceExtension, &FileName);

    return STATUS_SUCCESS;
}
